﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------
#endregion

#region Usings

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using ScrumTable.Common;
using ScrumTable.Common.Collections;
using ScrumTable.Common.EventHandling;
using ScrumTable.Common.Logging;
using ScrumTable.DL.Data.Generic.Properties;

#endregion

namespace ScrumTable.DL.Data.Generic.Cache
{
    internal enum CacheWorkItemLinkCollection
    {
        /// <summary>
        /// Specifies that the parent collection changed.
        /// </summary>
        Parent = 0,

        /// <summary>
        /// Specifies that the child collection changed.
        /// </summary>
        Child
    }

    internal class CacheWorkItemLinkChangedEventArgs : EventArgs
    {
        /// <summary>
        /// Gets the change action which has been executed on an element.
        /// </summary>
        internal CacheElementListChangedEventArgs<CacheWorkItemLink> InnerEvent { get; set; }

        /// <summary>
        /// Specifies the changed changed collection.
        /// </summary>
        internal CacheWorkItemLinkCollection Collection { get; set; }
    }

    internal class CacheWorkItemAttachmentChangedEventArgs : EventArgs
    {
        /// <summary>
        /// Gets the change action which has been executed on an element.
        /// </summary>
        internal CacheElementListChangedEventArgs<CacheAttachment> InnerEvent { get; set; }
    }


    /// <summary>
    /// Represents a work item wrapper with configuration facilities. This
    /// means, that a work item may reference a settings file, which is used
    /// to add additional properties to a work item, which a workitem not
    /// nativley supports.
    /// </summary>
    public class CacheWorkItem : CacheElementBase
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------

        private readonly FastSmartWeakEvent<EventHandler<EventArgs>> _iterationPathInvalidateEvents = new FastSmartWeakEvent<EventHandler<EventArgs>>();
        private readonly FastSmartWeakEvent<EventHandler<CacheWorkItemLinkChangedEventArgs>> _parentLinksIntegrityInvalidateEvents = new FastSmartWeakEvent<EventHandler<CacheWorkItemLinkChangedEventArgs>>();
        private readonly FastSmartWeakEvent<EventHandler<CacheWorkItemLinkChangedEventArgs>> _childLinksIntegrityInvalidateEvents = new FastSmartWeakEvent<EventHandler<CacheWorkItemLinkChangedEventArgs>>();

        private readonly FastSmartWeakEvent<EventHandler<CacheWorkItemAttachmentChangedEventArgs>> _attachmentChangeEvents = new FastSmartWeakEvent<EventHandler<CacheWorkItemAttachmentChangedEventArgs>>();
        
        private CacheElementPropertyChangeWatcher _propertyChangeWatcher;
        private CacheMember _assignedMember;
        private CacheIteration _iteration;

        #endregion

        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------

        /// <summary>
        /// Returns true if the current element has changes. This property
        /// is independent from the elements children.
        /// </summary>
        public override bool CurrentIsDirty
        {
            get { return base.CurrentIsDirty || Settings.IsDirty; }
        }

        /// <summary>
        /// Returns true, if a bucket element of this instance is dirty.
        /// </summary>
        public override bool IsDirty
        {
            get
            {
                bool isDirty = base.IsDirty;

                if (isDirty || CurrentIsDirty || ParentLinks.IsDirty || ChildLinks.IsDirty || Attachments.IsDirty)
                    return true;

                return false;
            }
        }

        /// <summary>
        /// Gets the tfs id of the element. Caution: This id is unique in
        /// the current storage only.
        /// </summary>
        public override int Id
        {
            get { return base.Id; }
            protected set
            {
                base.Id = value;
                BackEndId = value.ToString();
            }
        }

        /// <summary>
        /// Gets the work item type.
        /// </summary>
        public CacheWorkItemType Type
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets a value from the cached work item data object.
        /// </summary>
        public object this[string key]
        {
            get { return TryGetValueWithSettings(key); }
            set { TrySetValueWithSettings(key, null, value); }
        }

        /// <summary>
        /// Gets/sets the associated iteration id. This must be a valid value of an iteration id.
        /// </summary>
        public int IterationId
        {
            get { return Values.TryGetTrackedValue<int>(IterationIdPropertyName); }
            private set
            {
                value = (value < 0) ? CacheIdentity.DefaultId : value;

                if (value != CacheIdentity.DefaultId && Project != null)
                {
                    PreCondition.AssertTrue(
                        Project.Iterations.TryGetById(value) != null,
                        string.Format(Resources.ExcIteartionPathNotFound, value));
                }
                TrackValueChange(IterationIdPropertyName, value);
            }
        }

        /// <summary>
        /// Gets/set the associated iteration instance.
        /// </summary>
        public CacheIteration Iteration
        {
            get
            {
                if (Project == null)  // clones are not associated to a project
                {
                    return _iteration;
                }
                return Project.Iterations.TryGetById(IterationId);
            }
            set
            {
                if (Project == null)  // clones are not associated to a project
                {
                    _iteration = value;
                }
                IterationId = (value != null) ? value.Id : CacheIdentity.DefaultId;
            }
        }

        /// <summary>
        /// Gets the associated work item (work flow) state.
        /// </summary>
        public string State
        {
            get
            {
                ICacheTrackedDictionaryBucket history = TryGetStateValue();
                string historyValue = (history != null) ? (history.Value as string) : null;

                return (historyValue ?? string.Empty);
            }
            set { TrySetStateValue(value); }
        }

        /// <summary>
        /// Gets the work item description.
        /// </summary>
        public string Description
        {
            get { return Values.TryGetTrackedValue<string>(DescriptionPropertyName); }
            set { TrackValueChange(DescriptionPropertyName, value); }
        }

        /// <summary>
        /// Gets the display name of the assigned member.
        /// </summary>
        public string AssignedTo
        {
            get { return Values.TryGetTrackedValue<string>(AssignedToPropertyName); }
            private set { TrackValueChange(AssignedToPropertyName, value); }
        }


        /// <summary>
        /// Resolves a member display name as object to a full key chain (internal id).
        /// </summary>
        /// <remarks>This method will result in no action if the work item is disconnected from a data context (e.g. a work item clone).</remarks>
        /// <returns>Returns internal key chain of the member or an empty key chain if it could not be found.</returns>
        public ValueChain<string> AssignedToMemberChain
        {
            get
            {
                CacheMember assignedToMember = AssignedToMember;

                if (assignedToMember != null && Project != null)
                {
                    return new ValueChain<string>(Project.UniqueId, assignedToMember.UniqueId);
                }
                return ValueChain<string>.Empty;
            }
            set
            {
                if (Project != null)
                {
                    AssignedToMember = (value != null) ? Project.Members.TryGetById(value.Top) : null;
                }
            }
        }

        /// <summary>
        /// Resolves a member display name as object to a key chain (internal id).
        /// </summary>
        /// <returns>Returns internal key chain of the member or an empty key chain if it could not be found.</returns>
        public CacheMember AssignedToMember
        {
            get
            {
                if (Project == null) // clones are not associated to a project
                {
                    return _assignedMember;
                }
                return Project.Members.TryGetBy(t => t.ReferenceName == AssignedTo);
            }
            set
            {
                if (Project == null) // clones are not associated to a project
                {
                    _assignedMember = value;
                }
                AssignedTo = (value != null) ? value.ReferenceName : string.Empty;
            }
        }

        /// <summary>
        /// Gets a list of parent workitem ids.
        /// </summary>
        public CacheElementList<CacheWorkItemLink> ParentLinks { get; private set; }

        /// <summary>
        /// Gets a list of parent workitem ids.
        /// </summary>
        public CacheWorkItemLink ParentLink
        {
            get { return ParentLinks.FirstAvailable(); }
        }

        /// <summary>
        /// Gets a list of child workitem ids.
        /// </summary>
        public CacheElementList<CacheWorkItemLink> ChildLinks { get; private set; }

        /// <summary>
        /// Gets an enumeration of changed settings.
        /// </summary>
        public IDictionary<string, ICollection<object>> ChangedSettings
        {
            get { return Settings.ToDictionary<object>(); }
        }

        /// <summary>
        /// Gets an enumeration of changed values/settings combinations.
        /// </summary>
        public IEnumerable<ICacheTrackedDictionaryBucket> ChangedBuckets
        {
            get
            {
                return from ICacheTrackedDictionaryBucket changedBucket in Values
                       let bucket = Settings.TryGetTrackedBucket(changedBucket.Name) ?? changedBucket
                       where bucket.IsDirty
                       select bucket;
            }
        }

        /// <summary>
        /// Gets the internal attachments list.
        /// </summary>
        public CacheElementList<CacheAttachment> Attachments
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the internal revisions list. This list will not be sync back
        /// to the data layer driver because they are read only in the generic
        /// data layer and filled by the data driver.
        /// </summary>
        public IList<CacheWorkItemRevision> Revisions
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the internal settings list.
        /// </summary>
        protected CacheTrackedDictionary Settings
        {
            get;
            private set;
        }

        private CacheElementPropertyChangeWatcher PropertyChangeWatcher
        {
            get
            {
                if (_propertyChangeWatcher == null)
                {
                    _propertyChangeWatcher = new CacheElementPropertyChangeWatcher(this);
                    _propertyChangeWatcher.BlockMultiplePropertyEvents(StatePropertyName);
                }
                return _propertyChangeWatcher;
            }
        }

        private const string IterationIdPropertyName = "IterationId";
        private string StatePropertyName { get; set; }
        private string DescriptionPropertyName { get; set; }
        private string AssignedToPropertyName { get; set; }

        #endregion

        #region Constructors / Destructor
        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// Initializes a new instance of the <see cref="CacheWorkItem"/> class. This
        /// constructor is used when instantiating and copying elements stored on the server.
        /// </summary>
        /// <param name="uniqueId">Specifies the unique id of the current work item.</param>
        /// <param name="type">Specifies the work item type.</param>
        /// <param name="id">Specifies the team foundation id.</param>
        /// <param name="name">Specfies the work item title.</param>
        /// <param name="revision">Specifies the current revision of the work item.</param>
        /// <param name="iterationId">Specifeis the parent iteration id.</param>
        /// <param name="state">Specifies the workflow state of the work item.</param>
        /// <param name="description">Specifies the work item description.</param>
        /// <param name="values">Specifies a set of values corresponding to the underlying Schema to load.</param>
        /// <param name="assignedTo">Specifies the display name of the assigned member. Provide an empty string if no member is assigned.</param>
        /// <param name="parentLinks">Specifies the parent links.</param>
        /// <param name="childLinks">Specifies the chhild links.</param>
        /// <param name="attachments">Specifies a list of attachments stored behind the current work item.</param>
        /// <param name="revisions">Specifies a list of revisions stored behind the current work item.</param>
        internal CacheWorkItem(
            string uniqueId,
            CacheWorkItemType type,
            int id,
            string name,
            int revision,
            int iterationId,
            string state,
            string description,
            string assignedTo,
            IDictionary<string, object> values,
            IEnumerable<CacheWorkItemLink> parentLinks,
            IEnumerable<CacheWorkItemLink> childLinks,
            IEnumerable<CacheAttachment> attachments,
            IEnumerable<CacheWorkItemRevision> revisions)
            : base(uniqueId, id, name, revision, namePropertyName: type.NameField.Schema.SchemaMappedName)
        {
            PreCondition.AssertNotNull(type, "type");
            PreCondition.AssertNotNull(values, "values");
            PreCondition.AssertNotNull(parentLinks, "parentIds");
            PreCondition.AssertNotNull(childLinks, "childIds");
            PreCondition.AssertNotNull(attachments, "attachments");
            PreCondition.AssertNotNull(revisions, "revisions");
            PreCondition.AssertNotNull(assignedTo, "assignedTo");
            PreCondition.AssertNotNullOrEmpty(state, "state");

            Type = type;
            InitPropertyNames();

            InitSettings();
            InitCollections(parentLinks, childLinks, attachments, revisions);
            InitDefaultValues();

            // value set order is important
            ImportValues(values);
            SetInitialTrackedValue(IterationIdPropertyName, CacheIdentity.DefaultId, iterationId);
            SetInitialTrackedValue(StatePropertyName, null, state);
            SetInitialTrackedValue(DescriptionPropertyName, null, description);
            SetInitialTrackedValue(AssignedToPropertyName, string.Empty, assignedTo);
        }

        /// <summary>
        /// This constructor is needed if you create a new WorkItem instance
        /// on client side which must be inserted into the backend system.
        /// </summary>
        /// <param name="uniqueId">Specifies the new unqiue id of the element.</param>
        /// <param name="type">Specifies the workitem type.</param>
        /// <param name="initDefaultValues">True to initialize the default values. This must not be done if a clone from a WorkItem is created.</param>
        internal CacheWorkItem(string uniqueId, CacheWorkItemType type, bool initDefaultValues = true)
            : base(uniqueId, CacheIdentity.DefaultId, CacheIdentity.DefaultUniqueId, CacheIdentity.DefaultRevision, namePropertyName: type.NameField.Schema.SchemaMappedName)
        {
            PreCondition.AssertNotNull(type, "type");

            IsSynchronized = false;
            Type = type;
            InitPropertyNames();

            InitSettings();
            InitCollections();

            if (initDefaultValues)
            {
                InitDefaultValues();
            }
        }


        private void InitCollections(IEnumerable<CacheWorkItemLink> parentLinks = null,
            IEnumerable<CacheWorkItemLink> childLinks = null,
            IEnumerable<CacheAttachment> attachments = null,
            IEnumerable<CacheWorkItemRevision> revisions = null)
        {
            Attachments = new CacheElementList<CacheAttachment>(attachments, this);
            ParentLinks = new CacheElementList<CacheWorkItemLink>(parentLinks, this, CacheElementCompareMethod.UniqueId | CacheElementCompareMethod.Equals);
            ChildLinks = new CacheElementList<CacheWorkItemLink>(childLinks, this, CacheElementCompareMethod.UniqueId | CacheElementCompareMethod.Equals);
            Revisions = new List<CacheWorkItemRevision>(revisions ?? new CacheWorkItemRevision[0]);

            ParentLinks.IntegrityInvalidate += OnParentLinksIntegrityInvalidate;
            ChildLinks.IntegrityInvalidate += OnChildLinksIntegrityInvalidate;

            ParentLinks.Change += OnParentLinksChange;
            ChildLinks.Change += OnChildLinksChange;
            Attachments.Change += OnAttachmentsChange;
        }

        private void InitSettings()
        {
            Settings = new CacheTrackedDictionary();
            Settings.PropertyChanged += OnTrackingPropertyChanged;
            Settings.PropertyChanging += OnTrackingPropertyChanging;

            foreach (CacheFieldDefinition field in Type.FieldDefinitions)
            {
                // PRECONDITION 1:
                if (!field.IsSchemaInjected)
                {
                    // the field hasn't been injected by the schema so it
                    // is available on the back end side. Thus the
                    // data must be stored using the Values collection.
                    continue;
                }

                // PRECONDITION 2:
                if (field.Schema.IsSchemaElementReadOnly)
                {
                    // do not store read only values back the the back end system.
                    // they must be calculated on ScrumTable side.
                    continue;
                }

                // store only fields which are injected by the schema and must
                // be stored into the back end system.
                Settings.SetTrackedValue(
                    field.Schema.SchemaMappedName,
                    field.BackEndFieldType.GetDefault(),
                    field.DefaultValue);
            }
        }

        private void InitPropertyNames()
        {
            StatePropertyName = Type.StateField.Schema.SchemaMappedName;
            DescriptionPropertyName = Type.DescriptionField.Schema.SchemaMappedName;
            AssignedToPropertyName = Type.MemberIdField.Schema.SchemaMappedName;
        }

        private void InitDefaultValues()
        {
            foreach (CacheFieldDefinition field in Type.FieldDefinitions)
            {
                // PRECONDITION 1:
                if (field.IsSchemaInjected)
                {
                    // injected schema field must be stored into the settings
                    continue;
                }

                // PRECONDITION 2:
                ICacheTrackedDictionaryBucket fieldBucket = Values.TryGetTrackedBucket(field.Schema.SchemaMappedName);

                if (fieldBucket != null && fieldBucket.HasValue)
                {
                    // do not overwrite already initialized values
                    continue;
                } 

                // now, init default value
                Values.SetTrackedValue(
                        field.Schema.SchemaMappedName,
                        field.BackEndFieldType.GetDefault(),
                        field.DefaultValue);
            }
        }

        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        /// <summary>
        /// Converts the internal stored Values into a dictionary. You will
        /// obtain the non predefined fields only. Predefined fields are for
        /// example the State / Name / Description fields.
        /// </summary>
        /// <returns>Returns the created read only dictionary.</returns>
        public IDictionary<string, object> ToValuesDictionary()
        {
            return Values.ToDictionary(valueName => !Type.NonPredefinedFieldDefinitionsIndexed.ContainsKey(valueName));
        }

        /// <summary>
        /// Converts the internal stored Settings into a dictionary.
        /// </summary>
        /// <returns>Returns the created read only dictionary.</returns>
        public IDictionary<string, ICollection<object>> ToSettingsDictionary()
        {
            return Settings.ToDictionary<object>();
        }

        /// <summary>
        /// Merges the values of the given object into the current cache
        /// element. The cache element must be of the same type as the
        /// current instance is.
        /// </summary>
        /// <param name="toMerge">Specifies the object to import.</param>
        /// <param name="saveProgressFeedback">Specifies the save feedback from the underlying system.</param>
        public override void MergeChanges(ICacheTrackedElement toMerge, ICacheSaveProgressFeedback saveProgressFeedback)
        {
            using (PropertyChangeWatcher.WatchForChangeEvents())
            {
                base.MergeChanges(toMerge, saveProgressFeedback);
            }
        }

        /// <summary>
        /// Clones the current and all identities below the current cache element
        /// and fills them into the target store.
        /// </summary>
        /// <param name="target">Specifies the store which should be filled with the identities.</param>
        public override void CloneIdentities(CacheIdentityStore target)
        {
            base.CloneIdentities(target);

            Attachments.CloneIdentities(target);
            ParentLinks.CloneIdentities(target);
            ChildLinks.CloneIdentities(target);
        }

        /// <summary>
        /// Removes the dirty flag. This should be done after a clone has
        /// been created of the current element so the changes should be
        /// commited to the server.
        /// </summary>
        public override void RemoveDirtyFlag()
        {
            base.RemoveDirtyFlag();
            ParentLinks.RemoveDirtyFlag();
            ChildLinks.RemoveDirtyFlag();
            Settings.RemoveDirtyFlag();
            Attachments.RemoveDirtyFlag();
        }

        /// <summary>
        /// Imports a set of settings as key/value pairs.
        /// </summary>
        /// <param name="settings">Specifies the settings to import.</param>
        /// <remarks>The import avoids value tracking.</remarks>
        public void ImportSettings(IDictionary<string, ICollection<object>> settings)
        {
            PreCondition.AssertNotNull(settings, "settings");

            foreach (KeyValuePair<string, ICollection<object>> settingsPair in settings)
            {
                if (settingsPair.Value.Count == 0) { continue; }

                CacheFieldDefinition settingsField = EnsureFieldAvailable(settingsPair.Key);

                if (settingsPair.Value.Count == 1)
                {
                    // there's a single settings value
                    TryTrackSettingChange(
                        settingsField,
                        settingsField.BackEndFieldType.GetDefault(),
                        settingsPair.Value.FirstOrDefault());
                }
                else
                {
                    // there's an original server value and a settings value
                    TryTrackSettingChange(
                        settingsField,
                        settingsPair.Value.First(),
                        settingsPair.Value.Second());
                }
            }
        }

        /// <summary>
        /// Releases managed (c# or .net) resources.
        /// </summary>
        protected override void ReleaseManagedResources()
        {
            base.ReleaseManagedResources();

            Settings.PropertyChanged -= OnTrackingPropertyChanged;
            Settings.PropertyChanging -= OnTrackingPropertyChanging;

            ChildLinks.IntegrityInvalidate -= OnChildLinksIntegrityInvalidate;
            ParentLinks.IntegrityInvalidate -= OnParentLinksIntegrityInvalidate;

            Attachments.Change -= OnAttachmentsChange;
            ParentLinks.Change -= OnParentLinksChange;
            ChildLinks.Change -= OnChildLinksChange;
        }

        /// <summary>
        /// Creates a new clone instance of the current object.
        /// </summary>
        /// <returns>Returns the created clone instance.</returns>
        protected override CacheElementBase CreateCloneInstance()
        {
            CacheWorkItem workItem = new CacheWorkItem(UniqueId, Type, false);
            workItem.Settings.ImportValues(Settings);
            workItem.ParentLinks.ImportValues(ParentLinks);
            workItem.ChildLinks.ImportValues(ChildLinks);
            workItem.Attachments.ImportValues(Attachments);
            workItem._assignedMember = (AssignedToMember != null) ? (CacheMember)AssignedToMember.Clone() : null;
            workItem._iteration = (Iteration != null) ? (CacheIteration)Iteration.Clone() : null;
            return workItem;
        }

        /// <summary>
        /// Tracks the change on the current element and executes the change
        /// action of the values that are not the same. This action will cause the
        /// changed flag to be set.
        /// </summary>
        /// <param name="propertyName">Specifies the name of the underlying property.</param>
        /// <param name="serverValue">Specifies the old value to set.</param>
        /// <param name="newValue">Specifies the new value to set.</param>
        protected void TrackSettingChange(string propertyName, object serverValue, object newValue)
        {
            if (IsContextConnected)
            {
                Settings.TrackChange(propertyName, serverValue, newValue);
                InvalidateIsSynchronized();
            }
            else
            {
                Settings.SetTrackedValue(propertyName, serverValue, newValue);
            }
        }

        /// <summary>
        /// Ensures that the iteration path of the current work item in the work
        /// item tree is correct. This will fire the OnIterationPathInvalidate() event.
        /// </summary>
        internal void InvalidateIterationPath()
        {
            OnIterationPathInvalidate(EventArgs.Empty);
        }

        private void InvalidateIsSynchronized()
        {
            IsSynchronized = !IsDirty;
        }

        #region State Procedures

        /// <summary>
        /// Gets the state value with the given key from the settings if 
        /// a setting with the given key exist. Otherwise a state value
        /// with the given key will be returned or an empty string.
        /// </summary>
        private ICacheTrackedDictionaryBucket TryGetStateValue()
        {
            if (Type.Workflow.IsStrict)
            {
                return Values.TryGetTrackedBucket(StatePropertyName);
            }
            return TryGetValueWithOverwrittenSettings(StatePropertyName);
        }

        /// <summary>
        /// Writes a state value into the values collection if a value with the given key
        /// exists. Otherwise a settings field will be used to store the value.
        /// </summary>
        /// <param name="value">Specifies the value to set.</param>
        private void TrySetStateValue(string value)
        {
            using (PropertyChangeWatcher.WatchForChangeEvents())
            {
                if (Type.Workflow.IsStrict)
                {
                    TrackValueChange(StatePropertyName, value);
                }
                else
                {
                    TrySetValueWithSettings(
                        StatePropertyName,
                        GetLastBackEndAllowedState(value),
                        value);
                }
            }
        }

        private string GetLastBackEndAllowedState(string newState)
        {
            if (Type.StateField.Schema.SchemaAllowedValueMapping.ContainsKey(newState)
                && Type.StateField.Schema.SchemaAllowedValues.Contains(Type.StateField.Schema.SchemaAllowedValueMapping[newState]))
            {
                // if a schema value mapping is present, store the last allowed mapping value
                return Type.StateField.Schema.SchemaAllowedValueMapping[newState];
            }
            else
            {
                // last allowed server state must be evaluated in order to track
                // if a change on the server has altered the state stored in the settings file

                if (Type.StateField.IsValidBackEndValue(newState))
                {
                    // the current (new) state is valid and an allowed value, take this
                    return newState;
                }
                if (TryGetStateValue().LastBackEndValue != null)
                {
                    // a last server value is available, take this
                    return (string)TryGetStateValue().LastBackEndValue;
                }

                // there's no server state available jet. we have to work with
                // the initial (default) state of the state workflow. this must be
                // a server side state.
                return State;                
            }
        }

        #endregion


        private ICacheTrackedDictionaryBucket TryGetValueWithOverwrittenSettings(string key)
        {
            ICacheTrackedDictionaryBucket settingsBucket = Settings.TryGetTrackedBucket(key);
            ICacheTrackedDictionaryBucket valueBucket = Values.TryGetTrackedBucket(key);

            if (settingsBucket != null // if the settings bucket exists
                && settingsBucket.HasValue // if the settings bucket is filled (settings are available for this field)
                && Equals(settingsBucket.LastBackEndValue, valueBucket.Value)) // condition for overwrite a value
            {
                /*
                 * if an appropriate settings value exists (and is filled) and the appropriate
                 * value is equal to the expected original value, the settings can be used.
                 * otherwise the imported settings will be ignored.
                 */
                return settingsBucket;
            }
            return valueBucket;
        }

        private object TryGetValueWithSettings(string key)
        {
            CacheFieldDefinition field = EnsureFieldAvailable(key);

            if (field.IsSchemaInjected)
            {
                // if the settings bucket exists and is filled (settings are available for this field)
                return Settings.TryGetTrackedValue(field.BackEndFieldType, key);
            }
            return Values.TryGetTrackedValue(field.BackEndFieldType, key);
        }

        private void TrySetValueWithSettings(string key, object serverValue, object newValue)
        {
            CacheFieldDefinition field = EnsureFieldAvailable(key);

            if (Settings.Contains(key))
            {
                TryTrackSettingChange(field, serverValue, newValue);
                TryTrackValueChange(field, serverValue, newValue);
            }
            else if (!TryTrackValueChange(field, serverValue, newValue))
            {
                #region Init default value (original value)
                
                if (Values.Contains(key))
                {
                    // set original value by Values collection
                    Settings.SetTrackedValue(key, serverValue, Values.TryGetTrackedValue<object>(key));
                }
                else if (field != null)
                {
                    // set original value by fields default value
                    Settings.SetTrackedValue(key, serverValue, field.DefaultValue);
                }
                else
                {
                    // set original value by null value
                    Settings.SetTrackedValue(key, serverValue, null);
                }
                #endregion

                // track last back-end / new value in cache
                TryTrackSettingChange(field, serverValue, newValue);
            }
        }

        protected void TryTrackSettingChange(CacheFieldDefinition field, object serverValue, object newValue)
        {
            TrackSettingChange(field.BackEndId, serverValue, newValue);
        }

        private bool TryTrackValueChange(CacheFieldDefinition field, object serverValue, object value)
        {
            bool isValidBackendValue = false;

            // injected fields must be stored into the settings, do not store them into the value fields
            if (field != null && !field.IsSchemaInjected) 
            {
                if (field.IsValidBackEndValue(value))
                {
                    TrackValueChange(field.BackEndId, value);
                    isValidBackendValue = true;
                }
                else if (field.IsValidBackEndValue(serverValue))
                {
                    TrackValueChange(field.BackEndId, serverValue);
                }
            }
            return isValidBackendValue;
        }
        
        private void SetInitialTrackedValue(string fieldId, object serverValue, object value)
        {
            CacheFieldDefinition fieldDef = Type.TryGetFieldByBackEndId(fieldId);

            if (fieldDef != null)
            {
                // fields with schema based values (associated to a field definition) must
                // be validated and after that written  into the values storage
                if (fieldDef.IsValidBackEndValue(value))
                {
                    Values.SetTrackedValue(fieldDef.BackEndId, serverValue, value);
                }
                else if (fieldDef.IsValidBackEndValue(serverValue))
                {
                    Values.SetTrackedValue(fieldDef.BackEndId, serverValue, serverValue);
                }
                else
                {
                    Logger<CacheWorkItem>.Warn(string.Format(Resources.LogWiInvalidValue, value, fieldId));

                    Values.SetTrackedValue(
                        fieldDef.BackEndId,
                        fieldDef.DefaultValue,
                        fieldDef.HasAllowedValues ? fieldDef.AllowedValues.First() : fieldDef.DefaultValue);
                }
            }
            else
            {
                // fields with no schema based value (field definition) can be written
                // directly into the values dictionary
                Values.SetTrackedValue(fieldId, serverValue, value);
            }
        }

        private CacheFieldDefinition EnsureFieldAvailable(string key)
        {
            CacheFieldDefinition field = Type.TryGetFieldByBackEndId(key);

            if (field == null)
            {
                throw new InvalidOperationException(string.Format(Resources.ExcWiInvalidKey, Type.Name, key));
            }
            return field;
        }


        private void ImportValues(IDictionary<string, object> toSet)
        {
            foreach (KeyValuePair<string, object> keyPairs in toSet)
            {
                CacheFieldDefinition valueField = EnsureFieldAvailable(keyPairs.Key);

                // PRECONDITION 1:
                if (valueField.IsSchemaInjected)
                {
                    // the field has been injected by the schema so it
                    // is not available on the back end side. Thus the
                    // data must be stored using the Settings collection.
                    continue;
                }

                // PRECONDITION 2:
                if (valueField.Schema.IsSchemaReference)
                {
                    // do not import values from referenced data objects.
                    // their key chain is based on objects on business
                    // layer level and thus not relevant for data layer
                    // imports.
                    continue;
                }

                // PRECONDITION 3:
                if (!Type.NonPredefinedFieldDefinitionsIndexed.ContainsKey(keyPairs.Key))
                {
                    // do not import predefined field values. They may contain
                    // special formatting (e.g. State) and must be set via the
                    // compiled setter-properties of the work item or via the
                    // work item constructor.
                    continue;
                }

                if (!TryTrackValueChange(
                    valueField,
                    valueField.BackEndFieldType.GetDefault(),
                    keyPairs.Value))
                {
                    Logger<CacheWorkItem>.Warn(string.Format(Resources.LogWiInvalidValue, keyPairs.Value, keyPairs.Key));
                }
            }
        }


        private void ForwardMerge(CacheWorkItem wiToMerge, ICacheSaveProgressFeedback saveProgressFeedback)
        {
            ParentLinks.MergeChanges(wiToMerge.ParentLinks, saveProgressFeedback);
            ChildLinks.MergeChanges(wiToMerge.ChildLinks, saveProgressFeedback);
            Settings.MergeChanges(wiToMerge.Settings, saveProgressFeedback);
            Attachments.MergeChanges(wiToMerge.Attachments, saveProgressFeedback);
            Revisions = new List<CacheWorkItemRevision>(wiToMerge.Revisions);
        }

        private void ForwardMergeWithRevisions(CacheWorkItem wiToMerge, ICacheSaveProgressFeedback saveProgressFeedback)
        {
            ForwardMerge(wiToMerge, saveProgressFeedback);
            Revisions = new List<CacheWorkItemRevision>(wiToMerge.Revisions);
        }

        #endregion

        #region Events
        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------

        /// <summary>
        /// Fires the PropertyChanged event.
        /// </summary>
        /// <param name="e">Specifies the event args to fire to the clients.</param>
        /// <returns>Returns true if the event has been fired.</returns>
        protected override bool OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (!PropertyChangeWatcher.IsPropertyEventBlocked(e.PropertyName))
            {
                if (e.PropertyName == IterationIdPropertyName)
                {
                    InvalidateIterationPath();
                }
                return base.OnPropertyChanged(e);
            }
            return false;
        }

        /// <summary>
        /// Fires the PropertyChanging event.
        /// </summary>
        /// <param name="e">Specifies the event args to fire to the clients.</param>
        /// <returns>Returns true if the event has been fired.</returns>
        protected override bool OnPropertyChanging(PropertyChangingEventArgs e)
        {
            if (e.PropertyName == IterationIdPropertyName)
            {
                InvalidateIterationPath();
            }

            if (!PropertyChangeWatcher.IsPropertyEventBlocked(e.PropertyName))
            {
                if (base.OnPropertyChanging(e))
                {
                    PropertyChangeWatcher.BlockPropertyEventIfRequired(e.PropertyName);
                    return true;
                }
            }
            return false;
        }


        /// <summary>
        /// This event will be fired after the iteration of the workitem has changed.
        /// </summary>
        internal event EventHandler<EventArgs> IterationPathIntegrityInvalidate
        {
            add { _iterationPathInvalidateEvents.Add(value); }
            remove { _iterationPathInvalidateEvents.Remove(value); }
        }

        /// <summary>
        /// This event will be fired after a parent link has changed.
        /// </summary>
        internal event EventHandler<CacheWorkItemLinkChangedEventArgs> ParentLinksIntegrityInvalidate
        {
            add { _parentLinksIntegrityInvalidateEvents.Add(value); }
            remove { _parentLinksIntegrityInvalidateEvents.Remove(value); }
        }

        /// <summary>
        /// This event will be fired after a child link has changed.
        /// </summary>
        internal event EventHandler<CacheWorkItemLinkChangedEventArgs> ChildLinksIntegrityInvalidate
        {
            add { _childLinksIntegrityInvalidateEvents.Add(value); }
            remove { _childLinksIntegrityInvalidateEvents.Remove(value); }
        }


        /// <summary>
        /// This event will be fired after a child attachment has changed.
        /// </summary>
        internal event EventHandler<CacheWorkItemAttachmentChangedEventArgs> AttachmentsChange
        {
            add { _attachmentChangeEvents.Add(value); }
            remove { _attachmentChangeEvents.Remove(value); }
        }


        /// <summary>
        /// Fires the ParentLinksIntegrityInvalidate event.
        /// </summary>
        /// <param name="args">Specifies the event args which should be passed to the event listeners.</param>
        private void OnParentLinksIntegrityInvalidate(CacheWorkItemLinkChangedEventArgs args)
        {
            _parentLinksIntegrityInvalidateEvents.Raise(this, args);
        }

        /// <summary>
        /// Fires the ChildLinksIntegrityInvalidate event.
        /// </summary>
        /// <param name="args">Specifies the event args which should be passed to the event listeners.</param>
        private void OnChildLinksIntegrityInvalidate(CacheWorkItemLinkChangedEventArgs args)
        {
            _childLinksIntegrityInvalidateEvents.Raise(this, args);
        }

        /// <summary>
        /// Fires the IterationPathIntegrityInvalidate event.
        /// </summary>
        /// <param name="args">Specifies the event args which should be passed to the event listeners.</param>
        private void OnIterationPathInvalidate(EventArgs args)
        {
            _iterationPathInvalidateEvents.Raise(this, args);
        }


        /// <summary>
        /// Fires the AttachmentsChange event.
        /// </summary>
        /// <param name="args">Specifies the event args which should be passed to the event listeners.</param>
        private void OnAttachmentsChange(CacheWorkItemAttachmentChangedEventArgs args)
        {
            InvalidateIsSynchronized(); 
            _attachmentChangeEvents.Raise(this, args);
        }


        /// <summary>
        /// An error occurred while saving the given cache element. This means that
        /// the given element must be merged back (if there are no changes) into
        /// the cache tree.
        /// </summary>
        /// <param name="toMerge">Specifies the cache element which caused the error.</param>
        /// <param name="saveProgressFeedback">Specifies the save feedback provided by the data driver.</param>
        protected override void OnMergeChangeError(CacheElementBase toMerge, ICacheSaveProgressFeedback saveProgressFeedback)
        {
            base.OnMergeChangeError(toMerge, saveProgressFeedback);

            ForwardMerge((CacheWorkItem)toMerge, saveProgressFeedback);
        }

        /// <summary>
        /// An element on server side has changed (which behaviour may caused an error). Its
        /// cache data must be invalidated in order to resynchronize with the server.
        /// </summary>
        /// <param name="toMerge">Specifies the cache element which data should be invalidated.</param>
        /// <param name="saveProgressFeedback">Specifies the save feedback provided by the data driver.</param>
        protected override void OnMergeChangeInvalidateData(CacheElementBase toMerge, ICacheSaveProgressFeedback saveProgressFeedback)
        {
            base.OnMergeChangeInvalidateData(toMerge, saveProgressFeedback);

            ForwardMergeWithRevisions((CacheWorkItem)toMerge, saveProgressFeedback);
        }

        /// <summary>
        /// An element has saved on server side. This means that data in the local storage may
        /// be invalidated if the change caused additional changes on server side. Workflow
        /// changes may cause additional value changes.
        /// </summary>
        /// <param name="toMerge">Specifies the cache element which data have ben saved.</param>
        /// <param name="saveProgressFeedback">Specifies the save feedback provided by the data driver.</param>
        protected override void OnMergeChangeSaved(CacheElementBase toMerge, ICacheSaveProgressFeedback saveProgressFeedback)
        {
            base.OnMergeChangeSaved(toMerge, saveProgressFeedback);

            ForwardMergeWithRevisions((CacheWorkItem)toMerge, saveProgressFeedback);
        }

        /// <summary>
        /// An element on server side has been deleted. The element in the cache must also be 
        /// deleted.
        /// </summary>
        /// <param name="toMerge">Specifies the cache element which has been deleted.</param>
        /// <param name="saveProgressFeedback">Specifies the save feedback provided by the data driver.</param>
        protected override void OnMergeChangeDeleted(CacheElementBase toMerge, ICacheSaveProgressFeedback saveProgressFeedback)
        {
            base.OnMergeChangeDeleted(toMerge, saveProgressFeedback);

            ForwardMerge((CacheWorkItem)toMerge, saveProgressFeedback);
        }


        private void OnAttachmentsChange(object sender, CacheElementListChangedEventArgs<CacheAttachment> e)
        {
            OnAttachmentsChange(new CacheWorkItemAttachmentChangedEventArgs { InnerEvent = e });
        }

        private void OnChildLinksChange(object sender, CacheElementListChangedEventArgs<CacheWorkItemLink> e)
        {
            InvalidateIsSynchronized();
        }

        private void OnParentLinksChange(object sender, CacheElementListChangedEventArgs<CacheWorkItemLink> e)
        {
            InvalidateIsSynchronized();
        }


        private void OnChildLinksIntegrityInvalidate(object sender, CacheElementListChangedEventArgs<CacheWorkItemLink> e)
        {
            OnChildLinksIntegrityInvalidate(new CacheWorkItemLinkChangedEventArgs { InnerEvent = e, Collection = CacheWorkItemLinkCollection.Child });
        }

        private void OnParentLinksIntegrityInvalidate(object sender, CacheElementListChangedEventArgs<CacheWorkItemLink> e)
        {
            OnParentLinksIntegrityInvalidate(new CacheWorkItemLinkChangedEventArgs { InnerEvent = e, Collection = CacheWorkItemLinkCollection.Parent });
        }

        #endregion



        //--------------------------------------------------------------------
        // Nested Classes
        //--------------------------------------------------------------------

        /// <summary>
        /// Contains the facilities to catch up multiple OnChanging / OnChanged
        /// events in order to fire a single OnChanging / OnChanged for each
        /// property only.
        /// </summary>
        private class CacheElementPropertyChangeWatcher
        {
            #region Properties
            //--------------------------------------------------------------------
            // Properties
            //--------------------------------------------------------------------

            private readonly CacheWorkItem _outer;

            /// <summary>
            /// A property is assigned to three states:
            ///  - "true"  : Property observing is registered and its events are blocked
            ///  - "false" : Property observing is registered and its events will be fired
            /// 
            /// If single is null, there's no blocked property registration.
            /// </summary>
            private readonly IDictionary<string, Single<bool>> _properties = new Dictionary<string, Single<bool>>();

            #endregion

            #region Constructors / Destructor

            //--------------------------------------------------------------------
            // Constructors / Destructor
            //--------------------------------------------------------------------

            internal CacheElementPropertyChangeWatcher(CacheWorkItem outer)
            {
                PreCondition.AssertNotNull(outer, "outer");

                _outer = outer;
            }

            #endregion

            #region Methods

            //--------------------------------------------------------------------
            // Methods
            //--------------------------------------------------------------------

            /// <summary>
            /// Returns true if the events of the given property has been blocked.
            /// </summary>
            /// <param name="propertyName">Specifies the name of the blocked property.</param>
            /// <returns></returns>
            internal bool IsPropertyEventBlocked(string propertyName)
            {
                PreCondition.AssertNotNull(propertyName, "propertyName");

                return _properties.ContainsKey(propertyName) && _properties[propertyName].Value;
            }

            /// <summary>
            /// Blocks the OnChanged / OnChanging events if the given property
            /// is registered to be blocked when fired multiple times.
            /// </summary>
            /// <param name="propertyName"></param>
            internal void BlockPropertyEventIfRequired(string propertyName)
            {
                PreCondition.AssertNotNull(propertyName, "propertyName");

                if (_properties.ContainsKey(propertyName))
                {
                    // block events until the DisposeAction is called
                    _properties[propertyName].Value = true;
                }
            }

            /// <summary>
            /// Captures the events of the registered properties and fires one
            /// OnChanged / OnChanging event for each changed property.
            /// </summary>
            /// <returns>Returns a disposable instance which will fire the OnChanged events on Dispose() call.</returns>
            internal IDisposable WatchForChangeEvents()
            {
                return new DisposeAction(FireEventsAndResetChangeWatcher);
            }

            /// <summary>
            /// Registers the given property for blocking multiple OnChanged / OnChanging events.
            /// </summary>
            /// <param name="propertyNames">Specifies the names of the properties to block.</param>
            internal void BlockMultiplePropertyEvents(params string[] propertyNames)
            {
                PreCondition.AssertNotNull(propertyNames, "propertyNames");

                propertyNames.ForEach(propertyName => _properties.Add(propertyName, new Single<bool>(false)));
            }

            private void FireEventsAndResetChangeWatcher()
            {
                foreach (string propertyName in _properties.Keys)
                {
                    if (IsPropertyEventBlocked(propertyName))
                    {
                        // unblock property, this means OnPropertyChanged() fires events
                        _properties[propertyName].Value = false;

                        // call blocked event method and raise the event only once
                        _outer.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
                    }
                }
            }

            #endregion
        }
    }
}